package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.vo.Catalog2Vo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;

@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //1、查出所有的分类
        List<CategoryEntity> entities = baseMapper.selectList(null);
        //2、组装成父子的树形结构
        //2.1 找到所有的一级分类
        List<CategoryEntity> levelMenu1 = entities.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0
                ).map(menu->{
                    menu.setChildren(getChildren(menu,entities));
                    return menu;
                }).sorted((munu1,menu2)->{
                    return munu1.getSort() - menu2.getSort();
                }).collect(Collectors.toList());
        return levelMenu1;
    }

    //在list中找到root菜单的子菜单
    private List<CategoryEntity> getChildren(CategoryEntity root,List<CategoryEntity> all){
        List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == root.getCatId();
        }).map(categoryEntity -> {
            categoryEntity.setChildren(getChildren(categoryEntity, all));
            return categoryEntity;
        }).sorted((munu1, menu2) -> {
            return (munu1.getSort()==null?0:munu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());

        return children;
    }

    @Override
    public void removeMenusByIds(List<Long> asList) {
        baseMapper.deleteBatchIds(asList);
    }



    @Override
    public Long[] findCategoryPath(Long catelogId) {
        List<Long> catelogs = new ArrayList<>();
        catelogs = this.getCategoryPath(catelogId,catelogs);
        Collections.reverse(catelogs);
        return catelogs.toArray(new Long[catelogs.size()]);
    }

    @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if(!StringUtils.isEmpty(category.getName())){
            //更新品牌分类表中的数据
            categoryBrandRelationService.updateCatelog(category.getCatId(),category.getName());
        }
    }


        /*
    1、每一个需要缓存的数据，我们都要来指定要放到哪个名字的缓存。【相当于缓存的分区（按照业务类型来进行分区）】
    2、代表当前方法的结果需要缓存，如果缓存中有，方法不调用；如果缓存中没有，会调用方法，并将方法返回的结果放入缓存
    3、默认行为
        1）如果缓存中有，方法不能调用
        2）key值默认生成，缓存的名字::simplekey []
        3）缓存的value的值默认使用jdk序列化机制，将序列化后的数据存储到redis
        4）默认缓存过期时间是-1（用户过期）

        自定义：
        1）指定缓存生成的key： 指定key-> spel表达式
        2）指定缓存的数据的过期时间：配置文件指定
        3）将数据保存为json格式：
    */
    @Cacheable(value = {"category"},key="'level1Categorys'")
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("getLevel1Categorys方法执行......");
        List<CategoryEntity> categoryEntities = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", "0"));
        return categoryEntities;
    }

    //会产生堆外内存溢出的异常 OutofDirectMemoryError
    // 1、SpringBoot2.0以后默认使用的是lettuce作为操作redis的客户端。它使用nettey进行网络通信
    // 2、lettuce bug 导致的堆外内存溢出
    // 3、1）升级luttuce客户端  2）切换客户端 jedis
    // redisTemplate
    // luttuce、jedis都是操作redis的底层客户端，提供了操作redis的api, Spring对luttuce和jedis再次进行了封装

    /**
     * 1、空结果缓存，解决缓存穿透问题
     * 2、设置随机过期时间，解决缓存雪崩问题
     * 3、加锁，解决缓存击穿问题
     *
     */

    @Override
    public Map<String,List<Catalog2Vo>> getCatalogJson() {
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        if(StringUtils.isEmpty(catalogJson)){
            log.info("缓存不命中，查询数据库......");
            Map<String, List<Catalog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithRedissonLock();
            return catalogJsonFromDb;
        }
        log.info("缓存命中，直接返回.....");
        Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });
        return result;

    }

    /**
     * 使用本地锁，来解决缓存击穿问题
     */
    public Map<String,List<Catalog2Vo>> getCatalogJsonFromDbWithLocalLock() {
        //只要是同一把锁，就能锁住使用这个锁的所有线程
        // synchronized (this)  SpringBoot项目中所有组件在容器中都是单例的
        // 本地锁：只能锁住当前进程，分布式情况下不能锁住所有请求

        synchronized (this){
            //得到锁之后，需要去缓存中查询一次，如果没有，才继续查询数据库
            String catalogJson = redisTemplate.opsForValue().get("catalogJson");
            if(StringUtils.isNotBlank(catalogJson)){
                Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
                return result;
            }
            log.info("search db....");
            //将多次数据库查询优化成查询一次
            List<CategoryEntity> allCategoryEntities = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>());
            List<CategoryEntity> level1Categorys = getLevel1Categorys();
            Map<String,List<Catalog2Vo>>  map = level1Categorys.stream().collect(Collectors.toMap(k-> k.getCatId().toString(),v->{
                //查到一级分类的所有二级分类
                List<CategoryEntity> category2Entities = getParent_cid(allCategoryEntities,v.getCatId());
                List<Catalog2Vo> catalog2Vos = null;
                if(category2Entities!=null){
                    catalog2Vos = category2Entities.stream().map(item -> {
                        Catalog2Vo catalog2Vo = new Catalog2Vo(v.getCatId().toString(), null, item.getCatId().toString(), item.getName());
                        //查找当前二级分类的所有三级分类
                        List<CategoryEntity> category3Entities = getParent_cid(allCategoryEntities,item.getCatId());
                        if(category3Entities!=null){
                            List<Catalog2Vo.Catalog3Vo> collect = category3Entities.stream().map(catalog3 -> {
                                Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(item.getCatId().toString(), catalog3.getCatId().toString(), catalog3.getName());
                                return catalog3Vo;
                            }).collect(Collectors.toList());
                            catalog2Vo.setCatalog3List(collect);
                        }
                        return catalog2Vo;
                    }).collect(Collectors.toList());
                }
                return catalog2Vos;
            }));
            String catalogJsonFromDbStr = JSON.toJSONString(map);
            redisTemplate.opsForValue().set("catalogJson",catalogJsonFromDbStr,1, TimeUnit.DAYS);
            return map;
        }
    }


    /*
      使用 redis 分布式锁来解决缓存击穿问题
     */
    public Map<String,List<Catalog2Vo>> getCatalogJsonFromDbWithRedisLock() {
        //1、占分布式锁
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        if(lock){
            //加锁成功
            Map<String, List<Catalog2Vo>> dataFromDb;
            try{
                dataFromDb = getCatalogJsonFromDb();
            }finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                //删除锁
                Long lock1 = redisTemplate.execute(new
                                DefaultRedisScript<Long>(script, Long.class)
                        , Arrays.asList("lock"), uuid);
            }
            return dataFromDb;
        }else{
            //加锁失败
            return getCatalogJsonFromDbWithRedisLock();
        }
    }


    /**
     * 使用 Redisson 分布式锁来解决缓存击穿问题
     *
     */
    public Map<String,List<Catalog2Vo>> getCatalogJsonFromDbWithRedissonLock() {
        RLock catalogJsonLock = redisson.getLock("catalogJsonLock");
        Map<String, List<Catalog2Vo>> dataFromDb;
        catalogJsonLock.lock();
        try{
            dataFromDb = getCatalogJsonFromDb();
        }finally {
            catalogJsonLock.unlock();
        }
        return dataFromDb;
    }


    public Map<String,List<Catalog2Vo>> getCatalogJsonFromDb(){
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        if(StringUtils.isNotBlank(catalogJson)){
            Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
            return result;
        }
        log.info("search db....");
        //将多次数据库查询优化成查询一次
        List<CategoryEntity> allCategoryEntities = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        List<CategoryEntity> level1Categorys = getLevel1Categorys();
        Map<String,List<Catalog2Vo>>  map = level1Categorys.stream().collect(Collectors.toMap(k-> k.getCatId().toString(),v->{
            //查到一级分类的所有二级分类
            List<CategoryEntity> category2Entities = getParent_cid(allCategoryEntities,v.getCatId());
            List<Catalog2Vo> catalog2Vos = null;
            if(category2Entities!=null){
                catalog2Vos = category2Entities.stream().map(item -> {
                    Catalog2Vo catalog2Vo = new Catalog2Vo(v.getCatId().toString(), null, item.getCatId().toString(), item.getName());
                    //查找当前二级分类的所有三级分类
                    List<CategoryEntity> category3Entities = getParent_cid(allCategoryEntities,item.getCatId());
                    if(category3Entities!=null){
                        List<Catalog2Vo.Catalog3Vo> collect = category3Entities.stream().map(catalog3 -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(item.getCatId().toString(), catalog3.getCatId().toString(), catalog3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        catalog2Vo.setCatalog3List(collect);
                    }
                    return catalog2Vo;
                }).collect(Collectors.toList());
            }
            return catalog2Vos;
        }));
        String catalogJsonFromDbStr = JSON.toJSONString(map);
        redisTemplate.opsForValue().set("catalogJson",catalogJsonFromDbStr,1, TimeUnit.DAYS);
        return map;
    }

    private List<CategoryEntity> getParent_cid(List<CategoryEntity> list,Long parent_cid) {
        List<CategoryEntity> collect = list.stream().filter(item -> {
            return item.getParentCid() == parent_cid;
        }).collect(Collectors.toList());
        return collect;
    }

    private List<Long> getCategoryPath(Long catelogId, List<Long> catelogs) {
        catelogs.add(catelogId);
        CategoryEntity categoryEntity = this.getById(catelogId);
        if(categoryEntity.getParentCid()!=0){
            getCategoryPath(categoryEntity.getParentCid(),catelogs);
        }
        return catelogs;
    }


}